Skip to content

[中文版本](/posts/c教程/zh-CN/07-贴近系统/065-Signals and Signal Handlers)

When you press Ctrl+C and your program stops, that’s not magic, it’s a signal. Signals are how the operating system tells your process that something important has happened.

They’re asynchronous, lightweight messages from the kernel or other processes. Your C program can catch, ignore, or handle them, giving you full control over shutdowns, interrupts, and errors.

Step 1. What Is a Signal? ​

A signal is an integer code sent to a process to notify it of an event.

SignalMeaningDefault Action
SIGINTInterrupt (Ctrl+C)Terminate
SIGTERMTermination requestTerminate
SIGKILLForced kill (cannot catch)Terminate immediately
SIGSEGVInvalid memory accessCore dump
SIGCHLDChild process exitedIgnore or handle
SIGALRMTimer expiredTerminate
SIGUSR1,SIGUSR2User-definedTerminate (unless handled)

You can list all signals:

kill -l

Output example:

1) SIGHUP  2) SIGINT  3) SIGQUIT  9) SIGKILL  15) SIGTERM  ...

Step 2. Sending Signals ​

Any process can send a signal to another using thekill() system call.

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
int main(void) {
    pid_t pid = getpid();
    printf("My PID: %d\n", pid);
    pause();  // wait for signal
}

Run this in one terminal:

./signal_wait

Then in another:

kill -SIGUSR1 <pid>

The program will wake up frompause() and terminate (default behavior for SIGUSR1).

Step 3. Installing a Signal Handler ​

You can override the default action by installing a handler function.

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handle_sigint(int sig) {
    printf("\nCaught signal %d (SIGINT). Exiting gracefully.\n", sig);
    _exit(0);
}
int main(void) {
    signal(SIGINT, handle_sigint);
    while (1) {
        printf("Running... Press Ctrl+C to stop.\n");
        sleep(1);
    }
}

Compile and run:

gcc sigint_demo.c -o sigint_demo
./sigint_demo

Output:

Running... Press Ctrl+C to stop.
Running... Press Ctrl+C to stop.
^C
Caught signal 2 (SIGINT). Exiting gracefully.

Step 4. Using sigaction() (Modern API) ​

signal() is simple but inconsistent across systems. The recommended modern interface issigaction().

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handler(int sig) {
    write(STDOUT_FILENO, "Caught signal\n", 14);
}
int main(void) {
    struct sigaction sa = {0};
    sa.sa_handler = handler;
    sigaction(SIGUSR1, &sa, NULL);
    printf("PID: %d\n", getpid());
    while (1) pause();
}

Send a signal:

kill -SIGUSR1 <pid>

Output:

Caught signal

Unlikesignal(), this version is reliable and reentrant-safe (you can call only async-safe functions likewrite() inside handlers).

Step 5. Ignoring and Resetting Signals ​

You can ignore a signal:

signal(SIGINT, SIG_IGN);

Or reset to default behavior:

signal(SIGINT, SIG_DFL);

This can be useful if you don’t want Ctrl+C to interrupt certain sections of code.

Step 6. Sending Signals to Other Processes ​

Example: parent signaling its child.

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
void child_handler(int sig) {
    printf("Child got signal %d\n", sig);
}
int main(void) {
    pid_t pid = fork();
    if (pid == 0) {
        signal(SIGUSR1, child_handler);
        while (1) pause();
    } else {
        sleep(1);
        printf("Parent sending SIGUSR1\n");
        kill(pid, SIGUSR1);
        sleep(1);
        kill(pid, SIGTERM);
    }
}

Output:

Parent sending SIGUSR1
Child got signal 10

Step 7. Blocking and Unblocking Signals ​

Sometimes you want to delay signal handling. You can usesigprocmask() to block signals temporarily.

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    sigprocmask(SIG_BLOCK, &set, NULL);
    printf("SIGINT blocked for 5 seconds...\n");
    sleep(5);
    printf("Unblocking now.\n");
    sigprocmask(SIG_UNBLOCK, &set, NULL);
    while (1) pause();
}

Press Ctrl+C during the block, nothing happens. Once unblocked, it terminates normally.

Step 8. Timers with alarm() and SIGALRM ​

You can set a timer that sends a signal after a delay.

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void handler(int sig) {
    printf("Timer expired!\n");
}
int main(void) {
    signal(SIGALRM, handler);
    alarm(3);  // after 3 seconds, send SIGALRM
    printf("Waiting...\n");
    pause();
}

Output:

Waiting...
Timer expired!

Step 9. Cleaning Up on Exit ​

Signals let you implement graceful cleanup (e.g., close files, delete temp files).

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void cleanup(int sig) {
    printf("\nCleaning up before exit...\n");
    unlink("tempfile.tmp");
    _exit(0);
}
int main(void) {
    signal(SIGINT, cleanup);
    open("tempfile.tmp", O_CREAT | O_WRONLY, 0644);
    while (1) {
        printf("Running... (Ctrl+C to exit)\n");
        sleep(1);
    }
}

Now pressing Ctrl+C removes the temporary file safely.

Tiny Code: Graceful Shutdown Server ​

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
volatile sig_atomic_t running = 1;
void stop(int sig) {
    running = 0;
}
int main(void) {
    signal(SIGINT, stop);
    printf("Server running. Press Ctrl+C to stop.\n");
    while (running) {
        printf("Handling request...\n");
        sleep(1);
    }
    printf("Server shutting down cleanly.\n");
}

Step 10. Why It Matters ​

Signals are essential for:

  • Graceful termination (Ctrl+C)
  • Timeouts and alarms
  • Child process monitoring (SIGCHLD)
  • Error handling (segmentation faults)
  • Daemon and server control

Every real-world Unix program, from editors to web servers, depends on correct signal handling for stability.

Try It Yourself ​

  1. Write a program that ignores SIGINT for 5 seconds, then restores default behavior.
  2. Catch SIGTERM and print “Termination requested”.
  3. Make a parent send SIGUSR1 to its child every second.
  4. Usealarm() to implement a timeout for user input.
  5. Add a signal handler to your shell that cleans up child processes before exit.

Next, you’ll learn how programs share and map memory directly, usingmmap(), a system call that powers databases, shared memory, and file-backed data structures.

本站总访问 — 次